#include <string>
#include <iomanip>
#include <numeric>
#include <termios.h>
#include <unistd.h>
#include <fcntl.h>

#include <android/log.h>
#include <sys/system_properties.h>

#include "serial/serial.h"

#include "Logcat.h"
#include "BA330CameraManager.h"
#include "BA330CameraServiceException.h"

#ifdef __cplusplus
extern "C" {
#endif


#define MESSAGE_HEADER_LOW    0x55    // 帧头低字节
#define MESSAGE_HEADER_HIGH   0xAA    // 帧头高字节
#define MESSAGE_TRAILER_LOW    0x5A    // 帧尾低字节
#define MESSAGE_TRAILER_HIGH   0xA5    // 帧尾高字节

#define MESSAGE_REQUEST_BUFFER_SIZE 10240
#define MESSAGE_RESPONSE_BUFFER_LENGTH 1024

#define REQUEST_HEADER_LENGTH 9
#define REQUEST_TRAILER_LENGTH 4

#define RESPONSE_HEADER_LENGTH 11
#define RESPONSE_TRAILER_LENGTH 4

static volatile uint16_t messageIndex = 0;
volatile bool isPrintLog = true;

#define THROW_EXCEPTION(exceptionClass, message) throw exceptionClass(__FILE_NAME__, \
__LINE__, (message) )

void logcatHex(uint8_t *data, size_t size) {
    for (size_t i = 0; i < size; i++) {
        __android_log_print(ANDROID_LOG_DEBUG, TAG, "0x%02X ", data[i]);
    }
}

inline static std::string int2Hex(int val) {
    std::stringstream ss;
    ss << "0x";
    // 整数转换为大写的十六进制字符串，且每个字节占用两个字符的宽度
    ss << std::hex << std::uppercase << std::setfill('0') << std::setw(2) << val;
    return ss.str();
}

inline static std::string hexJoin(std::vector<uint8_t> const &vec, std::string delim) {
    if (vec.empty()) {
        return std::string();
    }
    return accumulate(vec.begin() + 1,
                      vec.end(),
                      int2Hex(vec[0]),
                      [](const std::string &a, uint8_t b) {
                          return a + ", " + int2Hex(b);
                      });
}

static inline uint16_t calculateCrc(const uint8_t *data, const uint32_t length, const uint16_t init = 0xFFFF) {
    uint16_t result = init;
    for (uint32_t i = 0; i < length; ++i) {
        result ^= data[i];
        for (uint8_t j = 0; j < 8; ++j) {
            if (result & 1) {
                result = (result >> 1) ^ 0xA001;
            } else {
                result = result >> 1;
            }
        }
    }
    return ~result;
}

static inline bool isEnvPt(){
    char buildType[8] = {'\0'};
    __system_property_get("persist.vendor.ehome.enviroment", buildType);
    if (strcmp("pt", buildType) == 0) {
        return true;
    } else{
        return false;
    }
}

static inline bool initSerial(serial::Serial **serial) {
    if (isEnvPt()){
        isPrintLog = false;
    }else{
        isPrintLog = true;
    }

    std::vector<serial::PortInfo> cameraDevices = serial::list_ports();
    if (cameraDevices.empty()) {
        THROW_EXCEPTION(BA330ConnectionException, "/dev/ttyACM* device notfound !!!");
    }

    const char *serialCameraPort = cameraDevices[0].port.c_str();

    try {
        *serial = new serial::Serial(serialCameraPort, 4000000,
                                     serial::Timeout::simpleTimeout(30000));
        if (!(*serial)->isOpen()) {
            (*serial)->open();
        }
        (*serial)->flushInput();
        (*serial)->flushOutput();
    } catch (exception &e) {
        char errorMessage[128] = {'\0'};
        sprintf(errorMessage, "%s device open failed !!!", serialCameraPort);
        THROW_EXCEPTION(BA330ConnectionException, errorMessage);
    }
    return true;
}

static inline void releaseSerial(serial::Serial *serial) {
    if (serial != nullptr) {
        if (serial->isOpen()) {
            try {
                serial->close();
            } catch (exception &e) {
                LOGE("%s device open failed !!!", serial->getPort().c_str());
            }
        }
        delete serial;
        serial = nullptr;
    }
}

static inline void
doRequest(serial::Serial *serial, uint8_t requestId, uint8_t *requestBody,
          uint32_t requestBodyLength,
          uint8_t **responseBody, uint32_t *responseBodyLength) {
    auto *request = static_cast<uint8_t *>(malloc(
            REQUEST_HEADER_LENGTH + requestBodyLength + REQUEST_TRAILER_LENGTH));
    uint32_t index = 0;
    request[index++] = MESSAGE_HEADER_LOW;
    request[index++] = MESSAGE_HEADER_HIGH;
    request[index++] = messageIndex & 0xFF;
    request[index++] = (messageIndex >> 8) & 0xFF;
    request[index++] = requestId;
    request[index++] = requestBodyLength & 0xFF;
    request[index++] = (requestBodyLength >> 8) & 0xFF;
    request[index++] = (requestBodyLength >> 16) & 0xFF;
    request[index++] = (requestBodyLength >> 24) & 0xFF;
    if (requestBodyLength > 0 && requestBody != nullptr) {
        memcpy(&request[index], requestBody, requestBodyLength);
        index += requestBodyLength;
    }
    uint16_t crcResult = calculateCrc(request, index);
    request[index++] = crcResult & 0xFF;
    request[index++] = (crcResult >> 8) & 0xFF;
    request[index++] = MESSAGE_TRAILER_LOW;
    request[index++] = MESSAGE_TRAILER_HIGH;
    LOGD("%s request header ---> %s", serial->getPort().c_str(),
         hexJoin(std::vector<uint8_t>(request, request + REQUEST_HEADER_LENGTH), ", ").c_str());
//    LOGD("%s request body ---> %s", serial->getPort().c_str(),
//         hexJoin(std::vector<uint8_t>(requestBody, requestBody + requestBodyLength), ", ").c_str());
    LOGD("%s request trailer ---> %s", serial->getPort().c_str(),
         hexJoin(std::vector<uint8_t>(request + index - REQUEST_TRAILER_LENGTH, request + index),
                 ", ").c_str());
    try {
        serial->write(request, index);
    } catch (std::exception &e) {
        THROW_EXCEPTION(BA330CommunicationException, "device write failed !!!");
    }

    free(request);
    request = nullptr;

    if (serial->waitReadable()) {
        uint8_t header[11];
        size_t readCount = 0;
        try {
            readCount = serial->read(header, RESPONSE_HEADER_LENGTH);
        } catch (exception &e) {
            THROW_EXCEPTION(BA330CommunicationException, "device read failed !!!");
        }
        *responseBodyLength = header[7] | header[8] << 8 | header[9] << 16 |
                              header[10] << 24;
        size_t responseLength =
                RESPONSE_HEADER_LENGTH + *responseBodyLength + RESPONSE_TRAILER_LENGTH;
        auto *response = static_cast<uint8_t *>(malloc(responseLength));
        memcpy(response, header, RESPONSE_HEADER_LENGTH);

        for (;;) {
            try {
                if (responseLength - readCount > MESSAGE_RESPONSE_BUFFER_LENGTH) {
                    readCount += serial->read(response + readCount, MESSAGE_RESPONSE_BUFFER_LENGTH);
                } else {
                    readCount += serial->read(response + readCount, responseLength - readCount);
                    break;
                }
            } catch (exception &e) {
                THROW_EXCEPTION(BA330CommunicationException, "device read failed !!!");
            }
        }
        LOGD("%s response header ---> %s", serial->getPort().c_str(),
             hexJoin(std::vector<uint8_t>(response, response + RESPONSE_HEADER_LENGTH),
                     ", ").c_str());
//        LOGD("%s response body ---> %s", serial->getPort().c_str(),
//             hexJoin(std::vector<uint8_t>(response + RESPONSE_HEADER_LENGTH,
//                                          response + RESPONSE_HEADER_LENGTH + (*responseBodyLength)),
//                     ", ").c_str());
        LOGD("%s response trailer ---> %s", serial->getPort().c_str(),
             hexJoin(std::vector<uint8_t>(response + responseLength - RESPONSE_TRAILER_LENGTH,
                                          response + responseLength), ", ").c_str());
        // verify
        if (response[0] != MESSAGE_HEADER_LOW || response[1] != MESSAGE_HEADER_HIGH) {
            free(response);
            response = nullptr;
            THROW_EXCEPTION(BA330CommunicationException, "Response Header Verification Failed");
        }

        if (response[5] != 0x00) {
            char buf[128] = {'\0'};
            sprintf(buf, "Response Code Verification Failed, Unknown Exception: 0x%02X",
                    response[5]);
            free(response);
            response = nullptr;
            THROW_EXCEPTION(BA330CommunicationException, buf);
        }

        if (response[6] != requestId) {
            free(response);
            response = nullptr;
            THROW_EXCEPTION(BA330CommunicationException, "Response Id Verification Failed");
        }

        if (responseLength != readCount) {
            free(response);
            response = nullptr;
            THROW_EXCEPTION(BA330CommunicationException, "Response Length Verification Failed");
        }

        if (response[responseLength - 2] != MESSAGE_TRAILER_LOW ||
            response[responseLength - 1] != MESSAGE_TRAILER_HIGH) {
            free(response);
            response = nullptr;
            THROW_EXCEPTION(BA330CommunicationException, "Response Trailer Verification Failed");
        }

        uint16_t responseCrc = calculateCrc(response, 7);
        responseCrc = calculateCrc(reinterpret_cast<const uint8_t *>(responseBodyLength), 4,
                                   ~responseCrc);
        if (*responseBodyLength > 0) {
            responseCrc = calculateCrc(response + RESPONSE_HEADER_LENGTH, *responseBodyLength,
                                       ~responseCrc);
        }
        if ((responseCrc & 0xFF) != response[responseLength - 4] ||
            (responseCrc >> 8 & 0xFF) != response[responseLength - 3]) {
            free(response);
            response = nullptr;
            THROW_EXCEPTION(BA330CommunicationException, "Response CRC Verification Failed");
        }

        if (*responseBodyLength > 0) {
            *responseBody = static_cast<uint8_t *>(malloc(*responseBodyLength));
            memcpy((*responseBody), response + RESPONSE_HEADER_LENGTH, *responseBodyLength);
        }
        free(response);
        response = nullptr;
    } else {
        releaseSerial(serial);
        BA330::reboot();
        THROW_EXCEPTION(BA330CommunicationException, "Response Read Timeout");
    }
}

void BA330::getDeviceInfo(char **deviceInfo) {
    serial::Serial *serial = nullptr;
    initSerial(&serial);

    uint8_t *responseBody = nullptr;
    uint32_t responseBodyLength = 0;

    uint8_t requestBody[1] = {SERIAL_NUMBER_GET};
    doRequest(serial, MESSAGE_ID_SERIAL_NUMBER, requestBody,
              1, &responseBody, &responseBodyLength);
    releaseSerial(serial);

    *deviceInfo = static_cast<char *>(malloc(responseBodyLength + 1));
    memset(*deviceInfo, '\0', responseBodyLength + 1);
    memcpy(*deviceInfo, responseBody, responseBodyLength);
    if (responseBody != nullptr) {
        free(responseBody);
        responseBody = nullptr;
    }
}

void BA330::setSerialNumber(const char *serialNumber) {
    serial::Serial *serial = nullptr;
    initSerial(&serial);

    uint8_t *responseBody = nullptr;
    uint32_t responseBodyLength = 0;

    uint8_t requestBody[1024];
    requestBody[0] = SERIAL_NUMBER_SET;
    memcpy(requestBody + 1, serialNumber, strlen(serialNumber));
    uint32_t requestBodyLength = 1 + strlen(serialNumber);

    doRequest(serial, MESSAGE_ID_SERIAL_NUMBER, requestBody,
              requestBodyLength, &responseBody, &responseBodyLength);

    releaseSerial(serial);

    if (responseBody != nullptr) {
        free(responseBody);
        responseBody = nullptr;
    }
}

void BA330::setFile(uint8_t fileType, int sourceFd) {
    serial::Serial *serial = nullptr;
    initSerial(&serial);

    off_t current = lseek(sourceFd, 0, SEEK_CUR);
    off_t fileSize = lseek(sourceFd, 0, SEEK_END);
    lseek(sourceFd, current, SEEK_SET);

    // 1
    uint8_t *responseBody = nullptr;
    uint32_t responseBodyLength = 0;
    uint8_t requestBody[5 + MESSAGE_REQUEST_BUFFER_SIZE] = {0};
    requestBody[0] = 0x00;
    memcpy(requestBody + 1, &fileSize, 4);
    doRequest(serial, fileType, requestBody,
              5, &responseBody, &responseBodyLength);

    if (responseBody != nullptr) {
        free(responseBody);
        responseBody = nullptr;
    }

    // 2
    uint32_t packetCount = 0;
    if (fileSize % MESSAGE_REQUEST_BUFFER_SIZE == 0) {
        packetCount = fileSize / MESSAGE_REQUEST_BUFFER_SIZE;
    } else {
        packetCount = (fileSize / MESSAGE_REQUEST_BUFFER_SIZE) + 1;
    }
    for (uint32_t packetIndex = 0; packetIndex < packetCount; ++packetIndex) {
        LOGD("%s packetCount ---> %d, packetIndex ---> %d", serial->getPort().c_str(), packetCount, packetIndex);
        requestBody[0] = 0x01;
        memcpy(requestBody + 1, &packetIndex, 4);
        size_t fileRead = read(sourceFd, requestBody + 5, MESSAGE_REQUEST_BUFFER_SIZE);
        doRequest(serial, fileType, requestBody,
                  5 + fileRead, &responseBody, &responseBodyLength);

        if (responseBody != nullptr) {
            free(responseBody);
            responseBody = nullptr;
        }
    }

    // 3
    requestBody[0] = 0x02;
    memcpy(requestBody + 1, &packetCount, 4);
    doRequest(serial, fileType, requestBody,
              5, &responseBody, &responseBodyLength);
    if (responseBody != nullptr) {
        free(responseBody);
        responseBody = nullptr;
    }
    releaseSerial(serial);
}

void BA330::getFile(uint8_t fileType, const uint8_t *params, size_t paramsLength, int targetFd) {
    serial::Serial *serial = nullptr;
    initSerial(&serial);

    // 1
    uint8_t *responseBody = nullptr;
    uint32_t responseBodyLength = 0;

    uint8_t requestBody[8] = {0};
    requestBody[0] = 0x00;
    uint32_t requestBodyLength = 1;
    if (params != nullptr) {
        memcpy(requestBody + 1, params, paramsLength);
        requestBodyLength += paramsLength;
    }
    doRequest(serial, fileType, requestBody,
              requestBodyLength, &responseBody, &responseBodyLength);

    uint32_t fileSize = responseBody[1] | responseBody[2] << 8 | responseBody[3] << 16 |
                        responseBody[4] << 24;
    if (responseBody != nullptr) {
        free(responseBody);
        responseBody = nullptr;
    }

    // 2
    uint32_t packetCount;
    if (fileSize % MESSAGE_REQUEST_BUFFER_SIZE == 0) {
        packetCount = fileSize / MESSAGE_REQUEST_BUFFER_SIZE;
    } else {
        packetCount = (fileSize / MESSAGE_REQUEST_BUFFER_SIZE) + 1;
    }
    for (uint32_t packetIndex = 0; packetIndex < packetCount; ++packetIndex) {
        LOGD("%s packetCount ---> %d, packetIndex ---> %d", serial->getPort().c_str(), packetCount, packetIndex);
        requestBody[0] = 0x01;
        memcpy(requestBody + 1, &packetIndex, 4);
        responseBody = nullptr;
        responseBodyLength = 0;
        doRequest(serial, fileType, requestBody,
                  5, &responseBody, &responseBodyLength);
        write(targetFd, responseBody + 5, responseBodyLength - 5);
        if (responseBody != nullptr) {
            free(responseBody);
            responseBody = nullptr;
        }
    }

    // 3
    requestBody[0] = 0x02;
    memcpy(requestBody + 1, &packetCount, 4);
    doRequest(serial, fileType, requestBody,
              5, &responseBody, &responseBodyLength);
    if (responseBody != nullptr) {
        free(responseBody);
        responseBody = nullptr;
    }
    releaseSerial(serial);
}

int BA330::getKitSate() {
    serial::Serial *serial = nullptr;
    initSerial(&serial);
    uint8_t *responseBody = nullptr;
    uint32_t responseBodyLength = 0;
    doRequest(serial, MESSAGE_ID_GET_KIT_STATE, nullptr,
              0, &responseBody, &responseBodyLength);
    releaseSerial(serial);
    uint8_t state = *responseBody;
    if (responseBody != nullptr) {
        free(responseBody);
        responseBody = nullptr;
    }
    return state;
}

void BA330::takeFluoSequencePicture(const int32_t *exposureTimeArray, uint8_t exposureTimeArrayLength,
                                    const int *targetArrayFd, uint8_t targetArrayLength, uint8_t brightness) {

    serial::Serial *serial = nullptr;
    initSerial(&serial);

    uint8_t requestBody[256] = {0};
    uint32_t requestBodyLength = 0;
    requestBody[0] = 0x00;
    requestBodyLength = 1;
    memcpy(requestBody + requestBodyLength, exposureTimeArray, sizeof(uint32_t) * exposureTimeArrayLength);
    requestBodyLength += sizeof(uint32_t) * exposureTimeArrayLength;
    memcpy(requestBody + requestBodyLength, &brightness, sizeof(uint8_t));
    requestBodyLength += sizeof(uint8_t);

    uint8_t *responseBody = nullptr;
    uint32_t responseBodyLength = 0;

    doRequest(serial, MESSAGE_ID_TAKE_FLUO_SEQUENCE_PICTURE, requestBody,
              requestBodyLength, &responseBody, &responseBodyLength);

    uint32_t fileSize = responseBody[1] | responseBody[2] << 8 | responseBody[3] << 16 |
                        responseBody[4] << 24;
    if (responseBody != nullptr) {
        free(responseBody);
        responseBody = nullptr;
    }

    // 2
    uint32_t packetCount;
    if (fileSize % MESSAGE_REQUEST_BUFFER_SIZE == 0) {
        packetCount = fileSize / MESSAGE_REQUEST_BUFFER_SIZE;
    } else {
        packetCount = (fileSize / MESSAGE_REQUEST_BUFFER_SIZE) + 1;
    }

    for (uint8_t imageIndex = 0; imageIndex < exposureTimeArrayLength; ++imageIndex) {

        for (uint32_t packetIndex = 0; packetIndex < packetCount; ++packetIndex) {
            LOGD("%s image count -> %d image index -> %d, packet count -> %d packet index -> %d",
                 serial->getPort().c_str(),
                 exposureTimeArrayLength, imageIndex, packetCount, packetIndex);

            requestBody[0] = 0x01;
            requestBodyLength = 1;
            requestBody[1] = imageIndex;
            requestBodyLength += 1;
            memcpy(requestBody + requestBodyLength, &packetIndex, 4);
            requestBodyLength += sizeof(uint32_t);

            responseBody = nullptr;
            responseBodyLength = 0;
            doRequest(serial, MESSAGE_ID_TAKE_FLUO_SEQUENCE_PICTURE, requestBody,
                      requestBodyLength, &responseBody, &responseBodyLength);
            write(targetArrayFd[imageIndex], responseBody + 6, responseBodyLength - 6);
            if (responseBody != nullptr) {
                free(responseBody);
                responseBody = nullptr;
            }
        }

        // 3
        requestBody[0] = 0x02;
        requestBodyLength = 1;
        memcpy(requestBody + 1, &packetCount, sizeof(uint32_t));
        requestBodyLength += sizeof(uint32_t);
        doRequest(serial, MESSAGE_ID_TAKE_FLUO_SEQUENCE_PICTURE, requestBody,
                  requestBodyLength, &responseBody, &responseBodyLength);
        if (responseBody != nullptr) {
            free(responseBody);
            responseBody = nullptr;
        }
    }

    releaseSerial(serial);
}

void BA330::controlCommand(uint8_t command, const uint8_t *data, uint32_t dataLength) {
    serial::Serial *serial = nullptr;
    initSerial(&serial);

    uint8_t *responseBody = nullptr;
    uint32_t responseBodyLength = 0;

    uint8_t requestBody[2048];
    uint32_t requestBodyLength = 0;
    requestBody[0] = command;
    requestBodyLength++;

    if (data != nullptr) {
        memcpy(requestBody + 1, data, dataLength);
        requestBodyLength += dataLength;
    }

    doRequest(serial, MESSAGE_ID_CONTROL_COMMAND, requestBody,
              requestBodyLength, &responseBody, &responseBodyLength);

    releaseSerial(serial);

    if (responseBody != nullptr) {
        free(responseBody);
        responseBody = nullptr;
    }
}

void BA330::reboot() {
    int fd = open("/sys/class/ehome/v24-gpio/gpio_value", O_WRONLY);
    write(fd, "0", 1);
    close(fd);
}

void BA330::setTimestamp() {
    serial::Serial *serial = nullptr;
    initSerial(&serial);

    uint8_t *responseBody = nullptr;
    uint32_t responseBodyLength = 0;

    uint8_t requestBody[9];
    uint32_t requestBodyLength = 9;

    time_t now;
    struct tm *tm_now;
    time(&now);
    tm_now = localtime(&now);

    uint32_t year = tm_now->tm_year + 1900;
    requestBody[0] = year & 0xFF;
    requestBody[1] = (year >> 8) & 0xFF;
    requestBody[2] = (year >> 16) & 0xFF;
    requestBody[3] = (year >> 24) & 0xFF;
    requestBody[4] = tm_now->tm_mon + 1;
    requestBody[5] = tm_now->tm_mday;
    requestBody[6] = tm_now->tm_hour;
    requestBody[7] = tm_now->tm_min;
    requestBody[8] = tm_now->tm_sec;

    doRequest(serial, MESSAGE_ID_SET_TIMESTAMP, requestBody,
              requestBodyLength, &responseBody, &responseBodyLength);
    releaseSerial(serial);

    if (responseBody != nullptr) {
        free(responseBody);
        responseBody = nullptr;
    }
}


#ifdef __cplusplus
}
#endif